/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <cstddef>
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <memory>
#include <napi/native_common.h>
#include <napi/native_api.h>
#include <string.h>
#include <cstdio>
#include <cstdlib>
#include <string>
#include "iccreader_def.h"
#include "iccreader_srv_client.h"
#include "hilog_wrapper.h"
#include "iccreader_utils.h"
#include "cashdevice_napi.h"
#include "iccreader_observer.h"

namespace SwanLink {
namespace Finance {

thread_local static IccreaderSrvClient& g_iccreaderClient = IccreaderSrvClient::GetInstance();
std::shared_ptr<SearchResultObserver> g_search_observer = nullptr;

/**
 * @brief 定义接口函数
 * static napi_value XXXX(napi_env env, napi_callback_info info)
 * @param env 
 * @param info 
 * @return napi_value 
 */
static napi_value GetDeviceInfo(napi_env env, napi_callback_info info)
{
    napi_value this_arg = nullptr;
    void *priv_data = nullptr;
    size_t argc = 1;
    napi_value args[1] = {0};
    struct tTmpStruct{
        int fd;
        DeviceInfo devInfo;
        int ret;
    };

    tTmpStruct *pVar = new tTmpStruct();

    NAPI_CALL(env,napi_get_cb_info(env,info,&argc,args,&this_arg,&priv_data));
    NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments");
    NAPI_CALL(env,napi_get_value_int32(env,args[0],&pVar->fd));

    async_excute_work work_func = [](napi_env env,size_t argc,napi_value args[],void *data)->napi_status {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        int &fd = pVar->fd;
        DeviceInfo &devInfo = pVar->devInfo;
        int &ret = pVar->ret;

        ret = g_iccreaderClient.GetDeviceInfo(fd, devInfo);
    
        return napi_ok;
    };
    async_result_work result_func = [] (napi_env env,void *data)->napi_value {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        napi_value result;
        (void)pVar->ret;
        NAPI_CALL(env,napi_create_object(env, &result));
        NAPI_OBJECT_PROP_SET_WITH_LEN(result, string_utf8, "vendor", NAPI_AUTO_LENGTH, pVar->devInfo.vendor);
        NAPI_OBJECT_PROP_SET_WITH_LEN(result, string_utf8, "deviceId", NAPI_AUTO_LENGTH, pVar->devInfo.deviceId);
        NAPI_OBJECT_PROP_SET_WITH_LEN(result, string_utf8, "serial", NAPI_AUTO_LENGTH, pVar->devInfo.serial);
        NAPI_OBJECT_PROP_SET_WITH_LEN(result, string_utf8, "libver", NAPI_AUTO_LENGTH, pVar->devInfo.libver);
        NAPI_OBJECT_PROP_SET_WITH_LEN(result, string_utf8, "version", NAPI_AUTO_LENGTH, pVar->devInfo.version);
        return result;
    };
    async_free_work free_func = [] (napi_env env,void *data) {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        delete pVar;
    };
    AsyncCallback *async = new AsyncCallback(env,argc,args,work_func,result_func,free_func,
        pVar,
        this_arg,
        priv_data);
    return AsyncPromise(env,"GetDeviceInfo",async);
}
static napi_value IccIOCtrl(napi_env env, napi_callback_info info)
{
    napi_value this_arg = nullptr;
    void *priv_data = nullptr;
    size_t argc = 3;
    napi_value args[3] = {0};
    struct tTmpStruct{
        int fd;
        int type;
        int value;
        int ret;
    };

    tTmpStruct *pVar = new tTmpStruct();

    NAPI_CALL(env,napi_get_cb_info(env,info,&argc,args,&this_arg,&priv_data));
    NAPI_ASSERT(env, argc >= 3, "Wrong number of arguments");
    NAPI_CALL(env,napi_get_value_int32(env,args[0],&pVar->fd));
    NAPI_CALL(env,napi_get_value_int32(env,args[1],&pVar->type));
    NAPI_CALL(env,napi_get_value_int32(env,args[2],&pVar->value));

    async_excute_work work_func = [](napi_env env,size_t argc,napi_value args[],void *data)->napi_status {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        int &fd = pVar->fd;
        int &type = pVar->type;
        int &value = pVar->value;
        int &ret = pVar->ret;

        ret = g_iccreaderClient.IccIOCtrl(fd,(IOType&)type,value);
    
        return napi_ok;
    };
    async_result_work result_func = [] (napi_env env,void *data)->napi_value {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        napi_value result;
        NAPI_CALL(env,napi_create_int32(env,pVar->ret,&result));

        return result;
    };
    async_free_work free_func = [] (napi_env env,void *data) {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        delete pVar;
    };
    AsyncCallback *async = new AsyncCallback(env,argc,args,work_func,result_func,free_func,
        pVar,
        this_arg,
        priv_data);
    return AsyncPromise(env,"IccIOCtrl",async);
}
static napi_value ApduCommand(napi_env env, napi_callback_info info)
{
    napi_value this_arg = nullptr;
    void *priv_data = nullptr;
    size_t argc = 4;
    napi_value args[4] = {0};
    struct tTmpStruct{
        int fd;
        int type;
        int len;
        uint8_t *cmd;
        uint8_t cmd_out[256];
        int ret;
    };

    tTmpStruct *pVar = new tTmpStruct();

    NAPI_CALL(env,napi_get_cb_info(env,info,&argc,args,&this_arg,&priv_data));
    NAPI_ASSERT(env, argc >= 3, "Wrong number of arguments");
    NAPI_CALL(env,napi_get_value_int32(env,args[0],&pVar->fd));
    NAPI_CALL(env,napi_get_value_int32(env,args[1],&pVar->type));
    napi_typedarray_type type;
    napi_value buffer;
	size_t offset = 0;

    pVar->cmd = NULL;
    pVar->len = 0;
    DEBUG_NAPI_CALL(env,napi_get_typedarray_info(env, args[2], &type, (size_t*)&pVar->len, (void **)&pVar->cmd, &buffer, &offset));
	if ((type != napi_uint8_array)) {
        ICCREADER_HILOGE(ICCREADER_JS_NAPI, "%{public}s not Uint8Array type: %{public}d", __func__, type);
        return nullptr;
    }

    async_excute_work work_func = [](napi_env env,size_t argc,napi_value args[],void *data)->napi_status {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        int &fd = pVar->fd;
        int &type = pVar->type;
        int &len = pVar->len;
        uint8_t* cmd = pVar->cmd;
        uint8_t* cmd_out = pVar->cmd_out;
        int &ret = pVar->ret;

        ret = g_iccreaderClient.ApduCommand(fd, (IOType&)type,len,cmd,cmd_out);
    
        return napi_ok;
    };
    async_result_work result_func = [] (napi_env env,void *data)->napi_value {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        napi_value result;
        if (pVar->ret <= 0) {
            ICCREADER_HILOGE(ICCREADER_JS_NAPI, "get buffer size is 0"); 
            return nullptr;
        }
        void *arrayData = nullptr;
        napi_value arrayBuffer = nullptr;
        size_t bufferSize = pVar->ret;
        DEBUG_NAPI_CALL(env, napi_create_arraybuffer(env, pVar->ret, &arrayData, &arrayBuffer));
        if (memcpy(arrayData, reinterpret_cast<const void*>(pVar->cmd_out), bufferSize) == nullptr) {
            ICCREADER_HILOGE(ICCREADER_JS_NAPI, "failed to copy buffer"); 
            return nullptr;
        }
        DEBUG_NAPI_CALL(env,napi_create_typedarray(env,napi_uint8_array,pVar->ret, arrayBuffer, 0, &result));

        return result;
    };
    async_free_work free_func = [] (napi_env env,void *data) {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        if (pVar->cmd) {
            free(pVar->cmd);
        }
        delete pVar;
    };
    AsyncCallback *async = new AsyncCallback(env,argc,args,work_func,result_func,free_func,
        pVar,
        this_arg,
        priv_data);
    return AsyncPromise(env,"ApduCommand",async);
}
static napi_value CardSearch(napi_env env, napi_callback_info info)
{
    napi_value this_arg = nullptr;
    void *priv_data = nullptr;
    size_t argc = 3;
    napi_value args[3] = {0};
    struct tTmpStruct{
        int fd;
        int timeout;
        SearchResultObserver *result;
        int ret;
    };

    tTmpStruct *pVar = new tTmpStruct();

    NAPI_CALL(env,napi_get_cb_info(env,info,&argc,args,&this_arg,&priv_data));
    NAPI_ASSERT(env, argc >= 3, "Wrong number of arguments");
    NAPI_CALL(env,napi_get_value_int32(env,args[0],&pVar->fd));
    NAPI_CALL(env,napi_get_value_int32(env,args[1],&pVar->timeout));
    // NAPI_CALL(env,napi_get_value_array(env,args[2],&pVar->result));
    if (g_search_observer == nullptr) {
        g_search_observer = std::make_shared<SearchResultObserver>();
    }
    pVar->result = g_search_observer.get();
    pVar->result->PushObserver(env, args[2]);

    async_excute_work work_func = [](napi_env env,size_t argc,napi_value args[],void *data)->napi_status {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        int &fd = pVar->fd;
        int &timeout = pVar->timeout;
        SearchResult* result = pVar->result;
        int &ret = pVar->ret;

        ret = g_iccreaderClient.CardSearch(fd,timeout,result);
    
        return napi_ok;
    };
    async_result_work result_func = [] (napi_env env,void *data)->napi_value {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        napi_value result;
        NAPI_CALL(env,napi_create_int32(env,pVar->ret,&result));

        return result;
    };
    async_free_work free_func = [] (napi_env env,void *data) {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        delete pVar;
    };
    AsyncCallback *async = new AsyncCallback(env,argc,args,work_func,result_func,free_func,
        pVar,
        this_arg,
        priv_data);
    return NapiCall(env,async);
}
static napi_value CardStopSearch(napi_env env, napi_callback_info info)
{
    napi_value this_arg = nullptr;
    void *priv_data = nullptr;
    size_t argc = 1;
    napi_value args[1] = {0};
    struct tTmpStruct{
        int fd;
        int ret;
    };

    tTmpStruct *pVar = new tTmpStruct();

    NAPI_CALL(env,napi_get_cb_info(env,info,&argc,args,&this_arg,&priv_data));
    NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments");
    NAPI_CALL(env,napi_get_value_int32(env,args[0],&pVar->fd));

    async_excute_work work_func = [](napi_env env,size_t argc,napi_value args[],void *data)->napi_status {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        int &fd = pVar->fd;
        int &ret = pVar->ret;

        ret = g_iccreaderClient.CardStopSearch(fd);
        if (g_search_observer != nullptr) {
            g_search_observer->PopObserver();
        }
    
        return napi_ok;
    };
    async_result_work result_func = [] (napi_env env,void *data)->napi_value {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        napi_value result;
        NAPI_CALL(env, napi_create_int32(env,pVar->ret,&result));

        return result;
    };
    async_free_work free_func = [] (napi_env env,void *data) {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        delete pVar;
    };
    AsyncCallback *async = new AsyncCallback(env,argc,args,work_func,result_func,free_func,
        pVar,
        this_arg,
        priv_data);

    return NapiCall(env,async);
}
static napi_value RfCardActive(napi_env env, napi_callback_info info)
{
    napi_value this_arg = nullptr;
    void *priv_data = nullptr;
    size_t argc = 1;
    napi_value args[1] = {0};
    struct tTmpStruct{
        int fd;
        RfCardInfo cardInfo;
        int ret;
    };

    tTmpStruct *pVar = new tTmpStruct();

    NAPI_CALL(env,napi_get_cb_info(env,info,&argc,args,&this_arg,&priv_data));
    NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments");
    NAPI_CALL(env,napi_get_value_int32(env,args[0],&pVar->fd));

    async_excute_work work_func = [](napi_env env,size_t argc,napi_value args[],void *data)->napi_status {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        int &fd = pVar->fd;
        RfCardInfo &cardInfo = pVar->cardInfo;
        int &ret = pVar->ret;

        ret = g_iccreaderClient.RfCardActive(fd, cardInfo);
    
        return napi_ok;
    };
    async_result_work result_func = [] (napi_env env,void *data)->napi_value {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        napi_value result;
        (void)pVar->ret;
        NAPI_CALL(env, napi_create_object(env, &result));
        NAPI_OBJECT_PROP_SET(result, int32, "type", pVar->cardInfo.type);
        NAPI_OBJECT_PROP_SET_WITH_LEN(result, string_utf8, "uid", NAPI_AUTO_LENGTH, pVar->cardInfo.uid);
        return result;
    };
    async_free_work free_func = [] (napi_env env,void *data) {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        delete pVar;
    };
    AsyncCallback *async = new AsyncCallback(env,argc,args,work_func,result_func,free_func,
        pVar,
        this_arg,
        priv_data);
    return AsyncPromise(env,"RfCardActive",async);
}
static napi_value RfCardDormancy(napi_env env, napi_callback_info info)
{
    napi_value this_arg = nullptr;
    void *priv_data = nullptr;
    size_t argc = 1;
    napi_value args[1] = {0};
    struct tTmpStruct{
        int fd;
        int ret;
    };

    tTmpStruct *pVar = new tTmpStruct();

    NAPI_CALL(env,napi_get_cb_info(env,info,&argc,args,&this_arg,&priv_data));
    NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments");
    NAPI_CALL(env,napi_get_value_int32(env,args[0],&pVar->fd));

    async_excute_work work_func = [](napi_env env,size_t argc,napi_value args[],void *data)->napi_status {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        int &fd = pVar->fd;
        int &ret = pVar->ret;

        ret = g_iccreaderClient.RfCardDormancy(fd);
    
        return napi_ok;
    };
    async_result_work result_func = [] (napi_env env,void *data)->napi_value {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        napi_value result;
        NAPI_CALL(env,napi_create_int32(env,pVar->ret,&result));

        return result;
    };
    async_free_work free_func = [] (napi_env env,void *data) {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        delete pVar;
    };
    AsyncCallback *async = new AsyncCallback(env,argc,args,work_func,result_func,free_func,
        pVar,
        this_arg,
        priv_data);

    return NapiCall(env,async);
}
static napi_value ScanData(napi_env env, napi_callback_info info)
{
    napi_value this_arg = nullptr;
    void *priv_data = nullptr;
    size_t argc = 2;
    napi_value args[2] = {0};
    struct tTmpStruct{
        int fd;
        DeviceConfig config;
        ResultData result;
        int ret;
    };

    tTmpStruct *pVar = new tTmpStruct();

    NAPI_CALL(env,napi_get_cb_info(env,info,&argc,args,&this_arg,&priv_data));
    NAPI_ASSERT(env, argc >= 2, "Wrong number of arguments");
    NAPI_CALL(env,napi_get_value_int32(env,args[0],&pVar->fd));

    NAPI_OBJECT_PROP_GET(args[1], int32, "devType", (int32_t*)&pVar->config.devType);
    NAPI_OBJECT_PROP_GET(args[1], int32, "timeout", (int32_t*)&pVar->config.timeout);
    NAPI_OBJECT_PROP_GET(args[1], int32, "mode", (int32_t*)&pVar->config.mode);
    ICCREADER_HILOGD(ICCREADER_JS_NAPI,  "deyType:%{public}d, timeout:%{public}d, mode:%{public}d", pVar->config.devType, pVar->config.timeout, pVar->config.mode); 
    if (pVar->config.devType == DT_SECARD) {
        NAPI_OBJECT_PROP_GET(args[1], int32, "config", (int32_t*)&pVar->config.other.findType);
    } else {
        size_t len = 0;
        NAPI_OBJECT_PROP_GET_WITH_LEN(args[1], string_utf8, "config", len, pVar->config.other.photo);
    }

    async_excute_work work_func = [](napi_env env,size_t argc,napi_value args[],void *data)->napi_status {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        int &fd = pVar->fd;
        DeviceConfig &config = pVar->config;
        ResultData &result = pVar->result;
        int &ret = pVar->ret;

        ret = g_iccreaderClient.ScanData(fd, config, result);
    
        return napi_ok;
    };
    async_result_work result_func = [] (napi_env env,void *data)->napi_value {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        napi_value result;
        (void)pVar->ret;
        result = ResultDataToNapi(env, pVar->result);
        return result;
    };
    async_free_work free_func = [] (napi_env env,void *data) {
        tTmpStruct *pVar = reinterpret_cast<tTmpStruct*>(data);
        delete pVar;
    };
    AsyncCallback *async = new AsyncCallback(env,argc,args,work_func,result_func,free_func,
        pVar,
        this_arg,
        priv_data);
    return AsyncPromise(env, "ScanData", async);
}


/**
 * @brief 定义枚举类型
 */
static napi_value EnumClassConstructor(napi_env env, napi_callback_info info)
{
    napi_value thisArg = nullptr;
    void* data = nullptr;

    napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, &data);

    napi_value global = nullptr;
    napi_get_global(env, &global);

    return thisArg;
}

static napi_value CreateEnumIOType(napi_env env,napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_CONST_VAR(CT_ANTENNA_SWITCH),
        DECLARE_CONST_VAR(CT_ANTENNA_POWER),
        DECLARE_CONST_VAR(CT_BEEP),
        DECLARE_CONST_VAR(CT_SLOT_MAIN),
        DECLARE_CONST_VAR(CT_SLOT_PSAM1),
        DECLARE_CONST_VAR(CT_SLOT_PSAM2),
        DECLARE_CONST_VAR(CT_SLOT_RF),
    };
    napi_value result = nullptr;
    napi_define_class(env, "IOType", NAPI_AUTO_LENGTH, EnumClassConstructor, nullptr,7, desc, &result);
    napi_set_named_property(env, exports, "IOType", result);
    return exports;
}
static napi_value CreateEnumDeviceType(napi_env env,napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_CONST_VAR(DT_SECARD),
        DECLARE_CONST_VAR(DT_SYNCARD),
        DECLARE_CONST_VAR(DT_SCAN),
        DECLARE_CONST_VAR(DT_KEYBOARD),
        DECLARE_CONST_VAR(DT_MAGNETIC),
    };
    napi_value result = nullptr;
    napi_define_class(env, "DeviceType", NAPI_AUTO_LENGTH, EnumClassConstructor, nullptr, 5, desc, &result);
    napi_set_named_property(env, exports, "DeviceType", result);
    return exports;
}

static napi_value CreateEnumVoiceMode(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_CONST_VAR(VOICE_NONE),
        DECLARE_CONST_VAR(VOICE_PLEASE),
        DECLARE_CONST_VAR(VOICE_NEW),
        DECLARE_CONST_VAR(VOICE_NEWAGAIN),
        DECLARE_CONST_VAR(VOICE_OLD),
    };
    napi_value result = nullptr;
    napi_define_class(env, "VoiceMode", NAPI_AUTO_LENGTH, EnumClassConstructor, nullptr,5, desc, &result);
    napi_set_named_property(env, exports, "VoiceMode", result);
    return exports;
}
EXTERN_C_START
/*
 * function for module exports
 */
static napi_value IccreaderInit(napi_env env, napi_value exports)
{
    ICCREADER_HILOGD(ICCREADER_JS_NAPI, "Enter");
    Common::ClientInit(dynamic_cast<CashDeviceClient*>(&g_iccreaderClient), env, exports);

    napi_property_descriptor desc[] = {
    DECLARE_NAPI_FUNCTION("GetSupportAccess",  Common::GetSupportAccess),
    DECLARE_NAPI_FUNCTION("Open",  Common::Open),
    DECLARE_NAPI_FUNCTION("Close",  Common::Close),
    DECLARE_NAPI_FUNCTION("QueryStatus",  Common::QueryStatus),
    DECLARE_NAPI_FUNCTION("Reset",  Common::Reset),
    DECLARE_NAPI_FUNCTION("GetDeviceInfo", GetDeviceInfo),
    DECLARE_NAPI_FUNCTION("IccIOCtrl", IccIOCtrl),
    DECLARE_NAPI_FUNCTION("ApduCommand", ApduCommand),
    DECLARE_NAPI_FUNCTION("CardSearch", CardSearch),
    DECLARE_NAPI_FUNCTION("CardStopSearch", CardStopSearch),
    DECLARE_NAPI_FUNCTION("RfCardActive", RfCardActive),
    DECLARE_NAPI_FUNCTION("RfCardDormancy", RfCardDormancy),
    DECLARE_NAPI_FUNCTION("ScanData", ScanData)
    };
    napi_value result;
    napi_create_object(env, &result);
    napi_define_properties(env, result, sizeof(desc) / sizeof(desc[0]), desc);
    napi_set_named_property(env, exports, "IccReader", result);
    
    CreateEnumIOType(env, exports);
    CreateEnumDeviceType(env, exports);
    CreateEnumVoiceMode(env, exports);

    ICCREADER_HILOGD(ICCREADER_JS_NAPI, "Success");

    return exports;
}
EXTERN_C_END

/*
 * Module definition
 */
static napi_module g_module = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = IccreaderInit,
    .nm_modname = "iccreader",
    .nm_priv = ((void*)0),
    .reserved = {0}
};
/*
 * Module registration
 */
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
    napi_module_register(&g_module);
}

}  // namespace Finance
}  // namespace SwanLink
